﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Lucene.Net.Store;
using System.ComponentModel.Composition;
using LuceneDirectory = Lucene.Net.Store.Directory;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;

namespace Steve.Lucene
{
    [Export(typeof(ILuceneDirectoryBuilder))]
    [PartCreationPolicy(CreationPolicy.Shared)]
    public sealed class CacheServiceDirectoryBuilder : ILuceneDirectoryBuilder
    {
        [Import]
        public ICacheService Cache { get; set; }

        public LuceneDirectory GetDirectory(SearchType searchType)
        {
            if (Cache == null) throw new InvalidOperationException("Cache not set, DI error.");

            return new CacheServiceDirectory(searchType, Cache);
        }
    }

    public sealed class CacheServiceDirectory : LuceneDirectory
    {
        SearchType _searchType;
        ICacheService _cache;

        public CacheServiceDirectory(SearchType searchType, ICacheService cache)
        {
            if (cache == null) throw new ArgumentNullException("cache");

            _searchType = searchType;
            _cache = cache;

            SetLockFactory(new NoLockFactory());
        }

        private string GetKey(string name)
        {
            return string.Format(CultureInfo.InvariantCulture, "{0}_{1}", _searchType, name);
        }

        public override void Close()
        {
        }

        public override IndexOutput CreateOutput(string name)
        {
            EnsureOpen();

            var value = GetValue(name);
            return new ByteArrayIndexOutput(value == null ? null : value.Data, data => PutValue(name, new CacheWrapper() { Data = data, Modified = DateTime.Now }));
        }

        public override void DeleteFile(string name)
        {
            EnsureOpen();
            _cache.Invalidate(CacheType.SearchIndex, GetKey(name));
        }

        private CacheWrapper GetValue(string name)
        {
            return _cache.GetValue<CacheWrapper>(CacheType.SearchIndex, GetKey(name));
        }

        private void PutValue(string name, CacheWrapper value)
        {
            _cache.PutValue(CacheType.SearchIndex, GetKey(name), value, null);
        }

        public override bool FileExists(string name)
        {
            EnsureOpen();
            return GetValue(name) != null;
        }

        public override long FileLength(string name)
        {
            EnsureOpen();
            return GetValue(name).Data.Length;
        }

        public override long FileModified(string name)
        {
            EnsureOpen();
            return (long)GetValue(name).Modified.ToUniversalTime().Subtract(new DateTime(1970, 1, 1, 0, 0, 0)).TotalMilliseconds;
        }

        [Obsolete("Obsolete in base")]
        public override string[] List()
        {
            EnsureOpen();

            var keys = from k in _cache.GetKeys(CacheType.SearchIndex)
                       where k.StartsWith(GetKey(string.Empty), StringComparison.Ordinal)
                       select k;

            return keys.ToArray();
        }

        public override IndexInput OpenInput(string name)
        {
            EnsureOpen();
            var val = GetValue(name);
            return new ByteArrayIndexInput(val == null ? null : val.Data);
        }

        /// <remarks>Not Atomic!</remarks>
        [Obsolete("Obsolete in base")]
        public override void RenameFile(string from, string to)
        {
            var value = GetValue(from);
            DeleteFile(from);
            PutValue(to, value);
        }

        public override void TouchFile(string name)
        {
            var value = GetValue(name);
            value.Modified = DateTime.Now;
            PutValue(name, value);
        }

        [Serializable]
        internal sealed class CacheWrapper
        {
            public CacheWrapper()
            {
                Data = new byte[] { };
                Modified = DateTime.Now;
            }

            public byte[] Data { get; set; }
            public DateTime Modified { get; set; }
        }
    }
}
